<?php
// +----------------------------------------------------------------------
// | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
// +----------------------------------------------------------------------
// | Copyright (c) 2006-2014 http://thinkphp.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Author: liu21st <liu21st@gmail.com>
// +----------------------------------------------------------------------

namespace Think;

/**
 * ThinkPHP 引导类
 */
class Think
{

  // 类映射
  private static $_map      = array();

  // 实例化对象
  private static $_instance = array();

  /**
   * 应用程序初始化
   * @access public
   * @return void
   */
  static public function start()
  {
    // 注册AUTOLOAD方法
    spl_autoload_register('Think\Think::autoload');
    // 设定错误和异常处理
    register_shutdown_function('Think\Think::fatalError');
    set_error_handler('Think\Think::appError');
    set_exception_handler('Think\Think::appException');

    // 初始化文件存储方式
    Storage::connect(STORAGE_TYPE);

    $runtimefile  = RUNTIME_PATH . APP_MODE . '~runtime.php';
    if (!APP_DEBUG && Storage::has($runtimefile)) {
      Storage::load($runtimefile);
    } else {
      if (Storage::has($runtimefile))
        Storage::unlink($runtimefile);
      $content =  '';
      // 读取应用模式
      $mode   =   include is_file(CONF_PATH . 'core.php') ? CONF_PATH . 'core.php' : MODE_PATH . APP_MODE . '.php';
      // 加载核心文件
      foreach ($mode['core'] as $file) {
        if (is_file($file)) {
          include $file;
          if (!APP_DEBUG) $content   .= compile($file);
        }
      }

      // 加载应用模式配置文件
      foreach ($mode['config'] as $key => $file) {
        is_numeric($key) ? C(load_config($file)) : C($key, load_config($file));
      }

      // 读取当前应用模式对应的配置文件
      if ('common' != APP_MODE && is_file(CONF_PATH . 'config_' . APP_MODE . CONF_EXT))
        C(load_config(CONF_PATH . 'config_' . APP_MODE . CONF_EXT));

      // 加载模式别名定义
      if (isset($mode['alias'])) {
        self::addMap(is_array($mode['alias']) ? $mode['alias'] : include $mode['alias']);
      }

      // 加载应用别名定义文件
      if (is_file(CONF_PATH . 'alias.php'))
        self::addMap(include CONF_PATH . 'alias.php');

      // 加载模式行为定义
      if (isset($mode['tags'])) {
        Hook::import(is_array($mode['tags']) ? $mode['tags'] : include $mode['tags']);
      }

      // 加载应用行为定义
      if (is_file(CONF_PATH . 'tags.php'))
        // 允许应用增加开发模式配置定义
        Hook::import(include CONF_PATH . 'tags.php');

      // 加载框架底层语言包
      L(include THINK_PATH . 'Lang/' . strtolower(C('DEFAULT_LANG')) . '.php');

      if (!APP_DEBUG) {
        $content  .=  "\nnamespace { Think\\Think::addMap(" . var_export(self::$_map, true) . ");";
        $content  .=  "\nL(" . var_export(L(), true) . ");\nC(" . var_export(C(), true) . ');Think\Hook::import(' . var_export(Hook::get(), true) . ');}';
        Storage::put($runtimefile, strip_whitespace('<?php ' . $content));
      } else {
        // 调试模式加载系统默认的配置文件
        C(include THINK_PATH . 'Conf/debug.php');
        // 读取应用调试配置文件
        if (is_file(CONF_PATH . 'debug' . CONF_EXT))
          C(include CONF_PATH . 'debug' . CONF_EXT);
      }
    }

    // 读取当前应用状态对应的配置文件
    if (APP_STATUS && is_file(CONF_PATH . APP_STATUS . CONF_EXT))
      C(include CONF_PATH . APP_STATUS . CONF_EXT);

    // 设置系统时区
    date_default_timezone_set(C('DEFAULT_TIMEZONE'));

    // 检查应用目录结构 如果不存在则自动创建
    if (C('CHECK_APP_DIR')) {
      $module     =   defined('BIND_MODULE') ? BIND_MODULE : C('DEFAULT_MODULE');
      if (!is_dir(APP_PATH . $module) || !is_dir(LOG_PATH)) {
        // 检测应用目录结构
        Build::checkDir($module);
      }
    }

    // 记录加载文件时间
    G('loadTime');
    // 运行应用
    App::run();
  }

  // 注册classmap
  static public function addMap($class, $map = '')
  {
    if (is_array($class)) {
      self::$_map = array_merge(self::$_map, $class);
    } else {
      self::$_map[$class] = $map;
    }
  }

  // 获取classmap
  static public function getMap($class = '')
  {
    if ('' === $class) {
      return self::$_map;
    } elseif (isset(self::$_map[$class])) {
      return self::$_map[$class];
    } else {
      return null;
    }
  }

  /**
   * 类库自动加载
   * @param string $class 对象类名
   * @return void
   */
  public static function autoload($class)
  {
    // 检查是否存在映射
    if (isset(self::$_map[$class])) {
      include self::$_map[$class];
    } elseif (false !== strpos($class, '\\')) {
      $name           =   strstr($class, '\\', true);
      if (in_array($name, array('Think', 'Org', 'Behavior', 'Com', 'Vendor')) || is_dir(LIB_PATH . $name)) {
        // Library目录下面的命名空间自动定位
        $path       =   LIB_PATH;
      } else {
        // 检测自定义命名空间 否则就以模块为命名空间
        $namespace  =   C('AUTOLOAD_NAMESPACE');
        $path       =   isset($namespace[$name]) ? dirname($namespace[$name]) . '/' : APP_PATH;
      }
      $filename       =   $path . str_replace('\\', '/', $class) . EXT;
      if (is_file($filename)) {
        // Win环境下面严格区分大小写
        if (IS_WIN && false === strpos(str_replace('/', '\\', realpath($filename)), $class . EXT)) {
          return;
        }
        include $filename;
      }
    } elseif (!C('APP_USE_NAMESPACE')) {
      // 自动加载的类库层
      foreach (explode(',', C('APP_AUTOLOAD_LAYER')) as $layer) {
        if (substr($class, -strlen($layer)) == $layer) {
          if (require_cache(MODULE_PATH . $layer . '/' . $class . EXT)) {
            return;
          }
        }
      }
      // 根据自动加载路径设置进行尝试搜索
      foreach (explode(',', C('APP_AUTOLOAD_PATH')) as $path) {
        if (import($path . '.' . $class))
          // 如果加载类成功则返回
          return;
      }
    }
  }

  /**
   * 取得对象实例 支持调用类的静态方法
   * @param string $class 对象类名
   * @param string $method 类的静态方法名
   * @return object
   */
  static public function instance($class, $method = '')
  {
    $identify   =   $class . $method;
    if (!isset(self::$_instance[$identify])) {
      if (class_exists($class)) {
        $o = new $class();
        if (!empty($method) && method_exists($o, $method))
          self::$_instance[$identify] = call_user_func(array(&$o, $method));
        else
          self::$_instance[$identify] = $o;
      } else
        self::halt(L('_CLASS_NOT_EXIST_') . ':' . $class);
    }
    return self::$_instance[$identify];
  }

  /**
   * 自定义异常处理
   * @access public
   * @param mixed $e 异常对象
   */
  static public function appException($e)
  {
    $error = array();
    $error['message']   =   $e->getMessage();
    $trace              =   $e->getTrace();
    if ('E' == $trace[0]['function']) {
      $error['file']  =   $trace[0]['file'];
      $error['line']  =   $trace[0]['line'];
    } else {
      $error['file']  =   $e->getFile();
      $error['line']  =   $e->getLine();
    }
    $error['trace']     =   $e->getTraceAsString();
    Log::record($error['message'], Log::ERR);
    // 发送404信息
    header('HTTP/1.1 404 Not Found');
    header('Status:404 Not Found');
    self::halt($error);
  }

  /**
   * 自定义错误处理
   * @access public
   * @param int $errno 错误类型
   * @param string $errstr 错误信息
   * @param string $errfile 错误文件
   * @param int $errline 错误行数
   * @return void
   */
  static public function appError($errno, $errstr, $errfile, $errline)
  {
    switch ($errno) {
      case E_ERROR:
      case E_PARSE:
      case E_CORE_ERROR:
      case E_COMPILE_ERROR:
      case E_USER_ERROR:
        ob_end_clean();
        $errorStr = "$errstr " . $errfile . " 第 $errline 行.";
        if (C('LOG_RECORD')) Log::write("[$errno] " . $errorStr, Log::ERR);
        self::halt($errorStr);
        break;
      default:
        $errorStr = "[$errno] $errstr " . $errfile . " 第 $errline 行.";
        self::trace($errorStr, '', 'NOTIC');
        break;
    }
  }

  // 致命错误捕获
  static public function fatalError()
  {
    Log::save();
    if ($e = error_get_last()) {
      switch ($e['type']) {
        case E_ERROR:
        case E_PARSE:
        case E_CORE_ERROR:
        case E_COMPILE_ERROR:
        case E_USER_ERROR:
          ob_end_clean();
          self::halt($e);
          break;
      }
    }
  }

  /**
   * 错误输出
   * @param mixed $error 错误
   * @return void
   */
  static public function halt($error)
  {
    $e = array();
    if (APP_DEBUG || IS_CLI) {
      //调试模式下输出错误信息
      if (!is_array($error)) {
        $trace          = debug_backtrace();
        $e['message']   = $error;
        $e['file']      = $trace[0]['file'];
        $e['line']      = $trace[0]['line'];
        ob_start();
        debug_print_backtrace();
        $e['trace']     = ob_get_clean();
      } else {
        $e              = $error;
      }
      if (IS_CLI) {
        exit(iconv('UTF-8', 'gbk', $e['message']) . PHP_EOL . 'FILE: ' . $e['file'] . '(' . $e['line'] . ')' . PHP_EOL . $e['trace']);
      }
    } else {
      //否则定向到错误页面
      $error_page         = C('ERROR_PAGE');
      if (!empty($error_page)) {
        redirect($error_page);
      } else {
        $message        = is_array($error) ? $error['message'] : $error;
        $e['message']   = C('SHOW_ERROR_MSG') ? $message : C('ERROR_MESSAGE');
      }
    }
    // 包含异常页面模板
    $exceptionFile =  C('TMPL_EXCEPTION_FILE', null, THINK_PATH . 'Tpl/think_exception.tpl');
    include $exceptionFile;
    exit;
  }

  /**
   * 添加和获取页面Trace记录
   * @param string $value 变量
   * @param string $label 标签
   * @param string $level 日志级别(或者页面Trace的选项卡)
   * @param boolean $record 是否记录日志
   * @return void|array
   */
  static public function trace($value = '[think]', $label = '', $level = 'DEBUG', $record = false)
  {
    static $_trace =  array();
    if ('[think]' === $value) { // 获取trace信息
      return $_trace;
    } else {
      $info   =   ($label ? $label . ':' : '') . print_r($value, true);
      $level  =   strtoupper($level);

      if ((defined('IS_AJAX') && IS_AJAX) || !C('SHOW_PAGE_TRACE')  || $record) {
        Log::record($info, $level, $record);
      } else {
        if (!isset($_trace[$level]) || count($_trace[$level]) > C('TRACE_MAX_RECORD')) {
          $_trace[$level] =   array();
        }
        $_trace[$level][]   =   $info;
      }
    }
  }
}
